package com.ruoyi.tiktok.service;

import com.alibaba.fastjson2.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.tiktok.domain.TFile;
import com.ruoyi.tiktok.domain.Task;
import com.ruoyi.tiktok.domain.TaskVideoRelation;
import com.ruoyi.tiktok.domain.vo.LaunchAppSchema;
import com.ruoyi.tiktok.mapper.TFileMapper;
import com.ruoyi.tiktok.mapper.TaskVideoRelationMapper;
import com.squareup.okhttp.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


@Service
public class TikTokService {
    private static final Logger log = LoggerFactory.getLogger(TikTokService.class);
    private static final int LENGTH = 16;
    private static final String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    private static final  String GRANT_TYPE = "grant_type";
    private static final  String CLIENT_CREDENTIAL = "client_credential";
    private static final  String CLIENT_KEY = "client_key";
    private static final  String CLIENT_SECRET = "client_secret";
    private static final  String ACCESS_TOKEN = "access_token";
    private static final  String DOUYIN_ACCESS_SIGNATURE = "douyin_access_signature";
    private static final  String DATA = "data";
    @Value("${douyin.oauthClientToken}")
    private String authorTakenAddress;
    @Value("${douyin.openTicket}")
    private String openTicketAddress;
    @Value("${douyin.clientKey}")
    private String clientKeyStr;
    @Value("${douyin.clientSecret}")
    private String clientSecretStr;
    @Value("${douyin.shareState)")
    private String shareStateAddr;
    @Autowired
    private TaskVideoRelationMapper taskVideoRelationMapper;

    @Autowired
    private TFileMapper tFileMapper;

    private final OkHttpClient okHttpClient;
    private final ObjectMapper objectMapper;
    private final RedisCache redisCache;
    private final RestTemplate restTemplate;

    public TikTokService(OkHttpClient okHttpClient, RedisCache redisCache, RestTemplate restTemplate) {
        this.okHttpClient = okHttpClient;
        this.objectMapper = new ObjectMapper();
        this.redisCache = redisCache;
        this.restTemplate = restTemplate;

    }

    /**
     * 生成随机的 字符串
     *
     * @return
     */
    private String generateNonceStr() {
        SecureRandom random = new SecureRandom();
        StringBuilder sb = new StringBuilder(LENGTH);
        for (int i = 0; i < LENGTH; i++) {
            int index = random.nextInt(CHARACTERS.length());
            sb.append(CHARACTERS.charAt(index));
        }
        return sb.toString();
    }


    /**
     * 根据 ticket 生成签名
     *
     * @param accessResultData
     * @return
     */
    public Map<String, String> genSignature(JSONObject accessResultData) {
        String nonceStr = generateNonceStr();
        Date date = new Date();
        String timestamp = String.valueOf(date.getTime());
        String openTicketValue = (String) accessResultData.get("ticket");

        HashMap<String, String> orderKeyMap = new HashMap<>();
        orderKeyMap.put("nonce_str", nonceStr);
        orderKeyMap.put("ticket", openTicketValue);
        orderKeyMap.put("timestamp", timestamp);


        // 使用 TreeMap 来对 HashMap 的键进行 ASCII 码排序
        TreeMap<String, String> sortedMap = new TreeMap<>(orderKeyMap);

        UriComponentsBuilder builder = UriComponentsBuilder.newInstance();
        sortedMap.forEach(builder::queryParam);

        String finalString1 = builder.build().toUriString().substring(1);
        log.info(finalString1);

        String sign = md5Encrypt(finalString1);
        orderKeyMap.put("signature", sign);
        orderKeyMap.put("clientKey", clientKeyStr);
        orderKeyMap.put("expire", "7200");  // 7200s

        return orderKeyMap;
    }

    /**
     * 对字符串进行 MD5 加密
     *
     * @param input
     * @return
     */
    private String md5Encrypt(String input) {
        try {
            // 获取 MD5 实例
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] hashInBytes = md.digest(input.getBytes(StandardCharsets.UTF_8));

            // 将字节数组转换为 16 进制表示的字符串
            StringBuilder sb = new StringBuilder();
            for (byte b : hashInBytes) {
                sb.append(String.format("%02x", b));  // 格式化为两位 16 进制数
            }
            return sb.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("MD5 algorithm not found", e);
        }
    }

    private AjaxResult requestDouyinToken() {
        HashMap<String, String> requestObject = new HashMap<>();
        requestObject.put(GRANT_TYPE, CLIENT_CREDENTIAL);
        requestObject.put(CLIENT_KEY, clientKeyStr);
        requestObject.put(CLIENT_SECRET, clientSecretStr);
        HttpEntity httpEntity = new HttpEntity(requestObject);
        ResponseEntity<AjaxResult> response = this.restTemplate.exchange(authorTakenAddress,
                HttpMethod.POST, httpEntity, AjaxResult.class);
        return response.getBody();
    }


    public JSONObject getClientTicket(String accessToken) throws IOException {
        Request request = new Request.Builder()
                .url(openTicketAddress)
                .get()
                .addHeader("access-token", accessToken)
                .addHeader(HttpHeaders.CONTENT_TYPE, "application/json")
                .build();
        Response response = okHttpClient.newCall(request).execute();
        JSONObject re = objectMapper.readValue(response.body().string(), JSONObject.class);
        return re;
    }

    public Map requestForSignature() throws IOException {
        HashMap cacheMap  = redisCache.getCacheObject(DOUYIN_ACCESS_SIGNATURE);
        if(cacheMap == null){
            //重新生成签名
            AjaxResult result = requestDouyinToken();
            if (result.isdouYiSuccess()) {
                Map dataObj = (Map) result.get(DATA);
                JSONObject accessResult = getClientTicket((String) dataObj.get(ACCESS_TOKEN));
                JSONObject accessResultData = accessResult.getJSONObject(DATA);
                if (0 == (Integer) accessResultData.get("error_code")) {
                    Map signMap = genSignature(accessResultData);
                    redisCache.setCacheObject(DOUYIN_ACCESS_SIGNATURE, signMap, 7180, TimeUnit.SECONDS);
                    return signMap;
                }
            }
        }  else {
            return cacheMap;
        }
        return null;
    }

    public LaunchAppSchema generateSchemaObj(Task task) {
        LaunchAppSchema launchAppSchema = null;
        try {
            Map signatureObj = requestForSignature();
            if (signatureObj != null) {
                launchAppSchema = LaunchAppSchema.baseLaunchAppSchema(signatureObj);
                launchAppSchema.setTitle(task.getTitle());
                if (task.getDestination().equalsIgnoreCase("toPublish")) {
                    launchAppSchema.setShareToPublish(1);
                }else {
                    launchAppSchema.setShareToType(1);// 转发到日常;
                }
                Pattern pattern = Pattern.compile("#\\S+"); // 匹配以#开头的非空字符
                Matcher matcher = pattern.matcher(task.getVideoTopic());
                ArrayList<String> hashTaglist = new ArrayList<>();
                while (matcher.find()) {
                    hashTaglist.add(matcher.group());
                }

                launchAppSchema.setHashTagList(hashTaglist);
                //launchAppSchema.setState("1234");

                TFile tFile  = selectOneVideoPath(task);
                if("404".equalsIgnoreCase(tFile.getFileUrl())){
                    // 绑定失败兜底用
                    launchAppSchema.setVideoPath("https://tuike-videos.oss-cn-nanjing.aliyuncs.com/videos/demo.mp4");
                }else {
                    log.info("find video path " + tFile.getFileUrl() );
                    launchAppSchema.setVideoPath(tFile.getFileUrl());
                }
                //设置封面
                launchAppSchema.setImagePath(tFile.getCoverImageAddress());
                launchAppSchema.setPoiId("1");
            }
        } catch (IOException | NullPointerException e) {
            // re-try 3 times
            throw new RuntimeException(e);
        }
        return launchAppSchema;
    }
    public JSONObject getShareId(String accessToken) throws IOException {
        Request request = new Request.Builder()
                .url(shareStateAddr)
                .get()
                .addHeader("access-token", accessToken)
                .addHeader(HttpHeaders.CONTENT_TYPE, "application/json")
                .build();
        Response response = okHttpClient.newCall(request).execute();
        JSONObject re = objectMapper.readValue(response.body().string(), JSONObject.class);
        return re;
    }


    public TFile selectOneVideoPath(Task task) {
        List<TaskVideoRelation> taskVideoRelations = taskVideoRelationMapper.getVideoList(task);

        if (taskVideoRelations.size()>0) {
            SecureRandom random = new SecureRandom();
            int videoId = random.nextInt(taskVideoRelations.size());
            TaskVideoRelation taskVideoRelation =  taskVideoRelations.get(0);
            TFile tFile = tFileMapper.selectTFileById(taskVideoRelation.getVideoId());
            log.info("random select video id:" + videoId);
            log.info("random select" +  tFile.toString());
            log.info("random select" +  tFile.getFileUrl());
            return tFile;
        }

        return null;
    }


}
